package com.lql.integrated.util.Redis;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author wangjian
 */
@Component
@AllArgsConstructor
public class RedisUtil {

    private final StringRedisTemplate redisTemplate;

    // Key（键），简单的key-value操作

    /**
     * 实现命令：TTL key，以秒为单位，返回给定 key的剩余生存时间(TTL, time to live)。
     *
     * @param key
     * @return
     */
    public long ttl(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 实现命令：expire 设置过期时间，单位秒
     *
     * @param key
     * @return
     */
    public void expire(String key, long timeout) {
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }
    /**
     * 指定缓存失效时间
     *
     * @param key 键
     * @param time 时间(秒) time>0
     */
    public boolean expire(RedisKeyEnum key, long time) {
        return redisTemplate.expire(key.getCode(), time, TimeUnit.SECONDS);
    }
    /**
     * 实现命令：INCR key，增加key一次
     *
     * @param key
     * @return
     */
    public long incr(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 实现命令：KEYS pattern，查找所有符合给定模式 pattern的 key
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 实现命令：DEL key，删除一个key
     *
     * @param key
     */
    public void del(String key) {
        redisTemplate.delete(key);
    }

    // String（字符串）

    /**
     * 实现命令：SET key value，设置一个key-value（将字符串值 value关联到 key）
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * add 无超时时间
     */
    public void set(RedisKeyEnum key, String val) {
        redisTemplate.opsForValue().set(key.getCode(), val);
    }



    /**
     * 实现命令：SET key value EX seconds，设置key-value和超时时间（秒）
     *
     * @param key
     * @param value
     * @param timeout （以秒为单位）
     */
    public void set(String key, String value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 实现命令：GET key，返回 key所关联的字符串值。
     *
     * @param key
     * @return value
     */
    public String get(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    // Hash（哈希表）

    /**
     * 实现命令：HSET key field value，将哈希表 key中的域 field的值设为 value
     *
     * @param key
     * @param field
     * @param value
     */
    public void hset(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    /**
     * 实现命令：HGET key field，返回哈希表 key中给定域 field的值
     *
     * @param key
     * @param field
     * @return
     */
    public String hget(String key, String field) {
        return (String) redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 实现命令：HDEL key field [field ...]，删除哈希表 key 中的一个或多个指定域，不存在的域将被忽略。
     *
     * @param key
     * @param fields
     */
    public void hdel(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 向一张hash表中取数据
     */
    public <T> T hget(RedisKeyEnum key, String item, Class<T> type) {
        return hget(key, item, null, 0, type);
    }

    public String hget(RedisKeyEnum key, String item) {
        return (String) redisTemplate.opsForHash().get(key.getCode(), item);
    }

    public List<Object> hget(RedisKeyEnum redisKeyEnum) {
        return redisTemplate.opsForHash().values(redisKeyEnum.getCode());
    }

    public void hdelete(RedisKeyEnum redisKeyEnum, String item) {
        redisTemplate.opsForHash().delete(redisKeyEnum.getCode(),item);
    }


    /**
     * 向一张hash表中取数据,如果不存在按照规则创建
     */
    public <T> T hget(RedisKeyEnum key, String item,
                      RedisGetMethodInterface redisGetMethodInterface,
                      Class<T> type) {
        return hget(key, item, redisGetMethodInterface, 0, type);
    }

    /**
     * 向一张hash表中取数据,如果不存在按照规则创建
     *
     * @param redisGetMethodInterface 规则
     * @param time 超时时间
     */
    public <T> T hget(RedisKeyEnum key, String item,
                      RedisGetMethodInterface redisGetMethodInterface,
                      long time, Class<T> type) {
        String data = (String) redisTemplate.opsForHash().get(key.getCode(), item);
        if (!StringUtils.isNotBlank(data)) {
            return JSONUtil.toBean(data, type);
        }
        if (redisGetMethodInterface != null) {
            return redisGetMethodInterfaceData(redisGetMethodInterface, key, item, time);
        }
        return null;
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     */
    public void hset(RedisKeyEnum key, String item, Object value, long time) {
        redisTemplate.opsForHash().put(key.getCode(), item, value);
        if (time > 0) {
            expire(key, time);
        }
    }


    public void hset(RedisKeyEnum key, String item, Object value) {
        hset(key, item, value, 0);
    }

    private <T> T redisGetMethodInterfaceData(RedisGetMethodInterface redisGetMethodInterface,
                                              RedisKeyEnum key, String item, long time) {
        T t = (T) redisGetMethodInterface.method();
        redisTemplate.opsForHash().put(key.getCode(), item, JSONUtil.toJsonStr(t));
        if (time > 0) {
            expire(key, time);
        }
        return t;
    }

    /**
     * 实现命令：HGETALL key，返回哈希表 key中，所有的域和值。
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hgetall(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    // List（列表）

    /**
     * 实现命令：LPUSH key value，将一个值 value插入到列表 key的表头
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后，列表的长度。
     */
    public long lpush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 实现命令：LPOP key，移除并返回列表 key的头元素。
     *
     * @param key
     * @return 列表key的头元素。
     */
    public String lpop(String key) {
        return (String) redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 实现命令：RPUSH key value，将一个值 value插入到列表 key的表尾(最右边)。
     *
     * @param key
     * @param value
     * @return 执行 LPUSH命令后，列表的长度。
     */
    public long rpush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }


    /**
     * 获取对应key的数据 没有按照规则插入
     */
    public <T> T get(RedisKeyEnum key, RedisGetMethodInterface redisGetMethodInterface,
                     long time, Class<T> type) {
        String data = redisTemplate.opsForValue().get(key.getCode());
        if (!StrUtil.isNotBlank(data)) {
            if (redisGetMethodInterface != null) {
                data = (String) redisGetMethodInterface.method();
                set(key, data);
                if (time > 0) {
                    expire(key, time);
                }
            } else {
                return null;
            }
        }
        String typeName = type.getName();
        String lang = "java.lang";
        if (StringUtils.contains(typeName, lang)) {
            return (T) data;
        }
        return JSONUtil.toBean(data, type);
    }

    /**
     * 获取对应key的数据 没有按照规则插入
     */
    public <T> T get(String key, RedisGetMethodInterface redisGetMethodInterface,
                     long time, Class<T> type) {
        String data = redisTemplate.opsForValue().get(key);
        if (!StrUtil.isNotBlank(data)) {
            if (redisGetMethodInterface != null) {
                data = (String) redisGetMethodInterface.method();
                set(key, data);
                if (time > 0) {
                    expire(key, time);
                }
            } else {
                return null;
            }
        }
        String typeName = type.getName();
        String lang = "java.lang";
        if (StringUtils.contains(typeName, lang)) {
            return (T) data;
        }
        return JSONUtil.toBean(data, type);
    }

}
